home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
L' Effet Pommier 3
/
L'Effet Pommier - Volume 03.iso
/
Programmation
/
gray image 2.1
/
image.cc
< prev
next >
Wrap
Text File
|
1995-10-08
|
22KB
|
794 lines
// This may look like C code, but it is really -*- C++ -*-
/*
************************************************************************
*
* Grayscale Image
* Implementation of the Primitive Operations
*
* The image is represented as a Pixmap, i.e. a matrix of pixels
* each of them specifies the gray level at a particular point
*
************************************************************************
*/
#pragma implementation "image.h"
#include "image.h"
#include "std.h"
#include <minmax.h>
#include <math.h>
/*
*------------------------------------------------------------------------
* Constructors and destructors
*/
void IMAGE::allocate(
const card no_rows, // No. of rows
const card no_cols, // No. of cols
const card depth // No. of bits per pixel
)
{
valid_code = IMAGE_val_code;
assure((ncols=no_cols) > 0, "Zero image width unexpected");
assure((nrows=no_rows) > 0, "Zero image height unexpected");
assure((bits_per_pixel=depth) > 0 && depth <= GRAY_MAXBIT,
"Zero or too large no. of bits per pixel");
name = "";
npixels = nrows * ncols;
assert( (scanrows = (GRAY **)calloc(nrows,sizeof(GRAY *))) != 0 );
assert( (pixels = (GRAY *)calloc(npixels,sizeof(GRAY))) != 0 );
register GRAY * pp = pixels;
register GRAY **sp = scanrows;
while( pp < pixels + npixels ) // Build the row index
*sp++ = pp, pp += ncols;
assert( sp == scanrows + nrows );
}
// Set a new image name
void IMAGE::set_name(const char * new_name)
{
if( name != 0 && name[0] != '\0' ) // Dispose of the previous image name
delete name;
if( new_name == 0 || new_name[0] == '\0' )
name = ""; // Image is anonymous now
else
name = new char[strlen(new_name)+1], strcpy(name,new_name);
}
// Routing constructor module
IMAGE::IMAGE(const IMAGE_CREATORS_1op op, const IMAGE& prototype)
{
switch(op)
{
case Expand:
_expand(prototype);
break;
case Shrink:
_shrink(prototype);
break;
default:
_error("Operation %d is not yet implemented",op);
}
}
IMAGE::~IMAGE(void) // Dispose the image struct
{
is_valid();
if( name != 0 && name[0] != '\0' )
delete name;
free(scanrows);
free(pixels);
valid_code = 0;
}
/*
*------------------------------------------------------------------------
* Single Image operations
*/
// Clip pixel values to
// [0,1<<bits_per_pixel-1].
// A pixel with the value outside that range
// (i.e. negative or too big) is set to
// 0 or 1<<bits_per_pixel-1, resp.
IMAGE& IMAGE::clip_to_intensity_range(void)
{
is_valid();
const int maxval = (1<<bits_per_pixel)-1;
register GRAY_SIGNED * cp = (GRAY_SIGNED *)pixels;
for(; cp < (GRAY_SIGNED *)pixels + npixels; cp++)
if( *cp < 0 )
*cp = 0;
else if( *cp > maxval )
*cp = maxval;
return *this;
}
// Perform a transformation
// pixel = |(signed)pixel|
// on all the pixels of the image
IMAGE& IMAGE::abs(void)
{
is_valid();
register GRAY_SIGNED * cp = (GRAY_SIGNED *)pixels;
for(; cp < (GRAY_SIGNED *)pixels + npixels; cp++)
if( *cp < 0 )
*cp = -(*cp);
return *this;
}
// Apply a user-defined action to each pixel
// while image is efficiently traversed
// row-by-row
IMAGE& IMAGE::apply(PixelAction& action)
{
is_valid();
action.nrows = nrows, action.ncols = ncols;
register GRAY * cp = pixels;
for(action.row=0; action.row<nrows; action.row++)
for(action.col=0; action.col<ncols; action.col++)
action.operation(*cp++);
return *this;
}
// Perform the histogram equalization
IMAGE& IMAGE::equalize(const int no_grays)
{
is_valid();
int orig_no_grays = 1 << bits_per_pixel;
assert( no_grays <= orig_no_grays );
int * const histogram = (int *)calloc(orig_no_grays,sizeof(int));
// Evaluate the histogram of an image
register GRAY_SIGNED * cp = (GRAY_SIGNED *)pixels;
for(; cp < (GRAY_SIGNED *)pixels + npixels; cp++)
{
if( *cp <= 0 )
*cp = 0;
else if( *cp >= orig_no_grays )
*cp = orig_no_grays-1;
histogram[*cp]++;
}
const int pixels_per_bin_optimal = (npixels + no_grays - 1)/no_grays;
const int gray_shade_subsample_factor =
(orig_no_grays + no_grays -1)/no_grays;
register int pixel;
int new_pixel, new_pixel_old;
int accumulated = 0;
int optimal_accumulated = pixels_per_bin_optimal;
// Mapping from pixels of original
// image to [new_pixel_old,new_pixel]
short * const look_up_table = (short *)malloc(orig_no_grays*sizeof(short));
// (actually, just to the center of
// this interval)
// Equalizing the histogram
for(pixel=0,new_pixel=0,new_pixel_old=0; pixel<orig_no_grays; pixel++)
{
accumulated += histogram[pixel];
while( accumulated > optimal_accumulated )
new_pixel += gray_shade_subsample_factor,
optimal_accumulated += pixels_per_bin_optimal;
assert( new_pixel < orig_no_grays );
look_up_table[pixel] = (new_pixel >
new_pixel_old+gray_shade_subsample_factor ?
(new_pixel + new_pixel_old)/2 :
new_pixel);
new_pixel_old = new_pixel;
}
// Update the image according to the LUT
for(cp = (GRAY_SIGNED *)pixels; cp < (GRAY_SIGNED *)pixels + npixels; cp++)
*cp = look_up_table[*cp];
free(histogram);
free(look_up_table);
return *this;
}
// Compute the 1. norm of the entire image
// SUM{ |(signed)pixel[i,j]| }
double IMAGE::norm_1(void) const
{
is_valid();
register GRAY_SIGNED * cp = (GRAY_SIGNED *)pixels;
double sum = 0;
for(; cp < (GRAY_SIGNED *)pixels + npixels; )
sum += ::abs(*cp++);
return sum;
}
// Compute the square of the 2. norm of
// the entire image
// SUM{ |(signed)pixel[i,j]|^2 }
double IMAGE::norm_2_sqr(void) const
{
is_valid();
register GRAY_SIGNED * cp = (GRAY_SIGNED *)pixels;
register double sum = 0;
for(; cp < (GRAY_SIGNED *)pixels + npixels; )
sum += ::sqr((long int)*cp++);
return sum;
}
// Compute the infinity norm of the
// entire image
// MAX{ |(signed)pixel[i,j]| }
int IMAGE::norm_inf(void) const
{
is_valid();
register GRAY_SIGNED * cp = (GRAY_SIGNED *)pixels;
register int maxp = 0;
for(; cp < (GRAY_SIGNED *)pixels + npixels; )
maxp = ::max(::abs(*cp++),maxp);
return maxp;
}
// Find extremum values of image pixels
Extrema::Extrema(const IMAGE& image)
: max_pixel(0,0), min_pixel(0,0)
{
image.is_valid();
max_value = min_value = image(0,0);
register GRAY_SIGNED * cp = (GRAY_SIGNED *)image.pixels;
register int i,j;
for(i=0; i<image.nrows; i++)
for(j=0; j<image.ncols; j++, cp++)
if( *cp > max_value )
max_value = *cp, max_pixel.row_val = i, max_pixel.col_val = j;
else if( *cp < min_value )
min_value = *cp, min_pixel.row_val = i, min_pixel.col_val = j;
}
// Normalize pixel values to be
// in range 0..1<<bits_per_pixel-1
IMAGE& IMAGE::normalize_for_display(void)
{
is_valid();
Extrema extrema(*this);
message("\nImage_normalization:"
"\n\tmin pixel value %d, max pixel value %d",
extrema.min(), extrema.max());
if( extrema.max() != extrema.min() )
{
double factor = (double)((1<<bits_per_pixel)-1) /
( extrema.max() - extrema.min() );
message("\n\tNormalization is as follows: (pixel - %d)*%g\n",
extrema.min(), factor);
register GRAY_SIGNED * cp = (GRAY_SIGNED *)pixels;
for(; cp < (GRAY_SIGNED *)pixels + npixels; cp++)
*cp = (int)( (*cp - extrema.min()) * factor + 0.5 );
}
return *this;
}
// Print some info about the image
void IMAGE::info(void) const
{
message("\nimage %dx%dx%d '%s' ",nrows,ncols,bits_per_pixel,name);
}
// Print the image as a table of pixel values
// (zeros are printed as dots)
void IMAGE::print(const char * title) const
{
is_valid();
message("\nImage %dx%dx%d '%s' is as follows\n\n",nrows,ncols,
bits_per_pixel,title);
register int i,j;
for(i=0; i<nrows; i++)
{
for(j=0; j<ncols; j++)
if( (*this)(i,j) == 0 )
message(" . ");
else
message("%4d ",(GRAY_SIGNED)(*this)(i,j));
message("\n");
}
message("Done\n");
}
/*
*------------------------------------------------------------------------
* Image-scalar operations
* Check to see if the preedicate "(signed)pixel operation scalar"
* holds for ALL the pixels of the image
*/
// is "(signed)pixel OP val" true for all
// pixels?
#define COMPARISON_WITH_SCALAR(OP) \
\
bool IMAGE::operator OP (const int val) const \
{ \
is_valid(); \
register GRAY_SIGNED * cp = (GRAY_SIGNED *)pixels; \
for(; cp < (GRAY_SIGNED *)pixels + npixels; ) \
if( !(*cp++ OP val) ) \
return false; \
\
return true; \
} \
COMPARISON_WITH_SCALAR(==)
COMPARISON_WITH_SCALAR(!=)
COMPARISON_WITH_SCALAR(<)
COMPARISON_WITH_SCALAR(<=)
COMPARISON_WITH_SCALAR(>)
COMPARISON_WITH_SCALAR(>=)
#undef COMPARISON_WITH_SCALAR
/*
*------------------------------------------------------------------------
* Image-scalar operations
* Modify every pixel according to the operation
*/
// For every pixel, do `pixel OP value`
#define COMPUTED_VAL_ASSIGNMENT(OP) \
\
IMAGE& IMAGE::operator OP (const int val) \
{ \
is_valid(); \
register GRAY * cp = pixels; \
while( cp < pixels+npixels ) \
*cp++ OP val; \
\
return *this; \
} \
COMPUTED_VAL_ASSIGNMENT(=)
COMPUTED_VAL_ASSIGNMENT(+=)
COMPUTED_VAL_ASSIGNMENT(-=)
COMPUTED_VAL_ASSIGNMENT(*=)
COMPUTED_VAL_ASSIGNMENT(|=)
COMPUTED_VAL_ASSIGNMENT(&=)
COMPUTED_VAL_ASSIGNMENT(^=)
#undef COMPUTED_VAL_ASSIGNMENT
// Shift the value of all the pixels
IMAGE& IMAGE::operator <<= (const int val)
{
is_valid();
if( ::abs(val) >= GRAY_MAXBIT )
_error("Very fishy shift factor: %d",val);
register GRAY_SIGNED * cp = (GRAY_SIGNED *)pixels;
while( cp < (GRAY_SIGNED *)pixels+npixels )
*cp++ <<= val;
return *this;
}
// Shift the value of all the pixels
IMAGE& IMAGE::operator >>= (const int val)
{
is_valid();
if( ::abs(val) >= GRAY_MAXBIT )
_error("Very fishy shift factor: %d",val);
register GRAY_SIGNED * cp = (GRAY_SIGNED *)pixels;
while( cp < (GRAY_SIGNED *)pixels+npixels )
*cp++ >>= val;
return *this;
}
/*
*------------------------------------------------------------------------
* Image-Image operations
* Modify the target image according to the operation
*/
IMAGE& IMAGE::operator = (const IMAGE& source)
{
are_compatible(*this,source);
memcpy(pixels,source.pixels,npixels*sizeof(GRAY));
return *this;
}
bool operator == (const IMAGE& im1, const IMAGE& im2)
{
are_compatible(im1,im2);
return (memcmp(im1.pixels,im2.pixels,im1.npixels*sizeof(GRAY)) == 0);
}
// Do "image OP src"
#define COMPUTED_ASSIGNMENT(OP) \
\
IMAGE& IMAGE::operator OP (const IMAGE& src) \
{ \
are_compatible(*this,src); \
\
register GRAY * sp = src.pixels; \
register GRAY * tp = pixels; \
while( tp < pixels + npixels ) \
*tp++ OP *sp++; \
\
return *this; \
} \
COMPUTED_ASSIGNMENT(+=)
COMPUTED_ASSIGNMENT(-=)
COMPUTED_ASSIGNMENT(|=)
COMPUTED_ASSIGNMENT(&=)
COMPUTED_ASSIGNMENT(^=)
#undef COMPUTED_ASSIGNMENT
#if 0
// Modified addition
// Target += scalar*Source
IMAGE& add(IMAGE& target, const int scalar,const IMAGE& source)
{
are_compatible(target,source);
register GRAY * sp = source.pixels;
register GRAY * tp = target.pixels;
while( tp < target.pixels + target.npixels )
*tp++ += scalar * *sp++;
return target;
}
// Shift the source to 'pos', clip it
// if necessary, multiply by the scalar,
// and add
IMAGE& IMAGE::shift_clip_add(rowcol pos, const int scalar, const IMAGE& source)
{
source.is_valid();
is_valid();
// Find an intersection of 'source'
// shifted to 'pos' (relative to
// the image) with the (target) image
// Only this rectangle will be
// affected by addition
rowcol s_orig(max(0,-pos.row()),max(0,-pos.col()));
rowcol t_orig(max(0,pos.row()),max(0,pos.col()));
// No. of rows in the intersection
const int irows = min(nrows - t_orig.row(),source.nrows - s_orig.row());
// No. of cols in the intersection
const int icols = min(ncols - t_orig.col(),source.ncols - s_orig.col());
if( irows <= 0 || icols <= 0 )
source.info(),
info(),
_error("The first image shifted by (%d,%d) does not intersect the other",
pos.row(),pos.col());
// See the explanation in the class
// Rectangle
const int s_inc_to_nextrow = source.ncols - icols;
const int t_inc_to_nextrow = ncols - icols;
register GRAY_SIGNED * sp =
(GRAY_SIGNED *)&(source.scanrows[s_orig.row()])[s_orig.col()];
register GRAY_SIGNED * tp =
(GRAY_SIGNED *)&(scanrows[t_orig.row()])[t_orig.col()];
register int i,j;
for(i=0; i<irows; i++, sp += s_inc_to_nextrow, tp += t_inc_to_nextrow)
for(j=0; j<icols; j++)
*tp++ += scalar * *sp++;
return *this;
}
#endif
// Evaluate the scalar product of two images
double operator * (const IMAGE& im1, const IMAGE& im2)
{
are_compatible(im1,im2);
register GRAY_SIGNED * p1 = (GRAY_SIGNED *)im1.pixels;
register GRAY_SIGNED * p2 = (GRAY_SIGNED *)im2.pixels;
register double sum = 0;
while( p1 < (GRAY_SIGNED *)im1.pixels + im1.npixels )
sum += *p1++ * *p2++;
return sum;
}
// Estimate the norm of the difference
// between two (signed) image
// SUM{ |(signed)pixel[i,j]| }
double norm_1(const IMAGE& im1, const IMAGE& im2)
{
are_compatible(im1,im2);
register GRAY_SIGNED * p1 = (GRAY_SIGNED *)im1.pixels;
register GRAY_SIGNED * p2 = (GRAY_SIGNED *)im2.pixels;
register double sum = 0;
while( p1 < (GRAY_SIGNED *)im1.pixels + im1.npixels )
sum += ::abs(*p1++ - *p2++);
return sum;
}
// SUM{ |(signed)pixel[i,j]|^2 }
double norm_2_sqr(const IMAGE& im1, const IMAGE& im2)
{
are_compatible(im1,im2);
register GRAY_SIGNED * p1 = (GRAY_SIGNED *)im1.pixels;
register GRAY_SIGNED * p2 = (GRAY_SIGNED *)im2.pixels;
register double sum = 0;
while( p1 < (GRAY_SIGNED *)im1.pixels + im1.npixels )
sum += ::sqr((long int)(*p1++ - *p2++));
return sum;
}
// MAX{ |(signed)pixel[i,j]| }
int norm_inf(const IMAGE& im1, const IMAGE& im2)
{
are_compatible(im1,im2);
register GRAY_SIGNED * p1 = (GRAY_SIGNED *)im1.pixels;
register GRAY_SIGNED * p2 = (GRAY_SIGNED *)im2.pixels;
register int maxp = 0;
while( p1 < (GRAY_SIGNED *)im1.pixels + im1.npixels )
maxp = max(maxp,::abs(*p1++ - *p2++));
return maxp;
}
void compare( // Compare the two images
const IMAGE& image1, // and print out the result of comparison
const IMAGE& image2,
const char * title )
{
register int i,j;
are_compatible(image1,image2);
message("\n\nComparison of two images %dx%dx%d\n%s\n",image1.nrows,
image1.ncols,image1.bits_per_pixel,title);
if( image1.bits_per_pixel != image2.bits_per_pixel )
message("\nNote, images have different depth, %d and %d\n",
image1.bits_per_pixel, image2.bits_per_pixel);
double norm1 = 0, norm2 = 0; // Norm of the images
double ndiff = 0; // Norm of the difference
int imax=0,jmax=0,difmax = -1; // For the pixels that differ most
// Image scanline pointer
register GRAY_SIGNED *rowp1 = (GRAY_SIGNED *)image1.pixels;
register GRAY_SIGNED *rowp2 = (GRAY_SIGNED *)image2.pixels;
bool im1_neg_pixel = false; // Flags whether negative pixels
bool im2_neg_pixel = false; // were encountered
for(i=0; i < image1.nrows; i++)
for(j=0; j < image1.ncols; j++)
{
int pv1 = *rowp1++;
int pv2 = *rowp2++;
int diff = abs(pv1-pv2);
if( pv1 < 0 )
im1_neg_pixel = true, pv1 = -pv1;
if( pv2 < 0 )
im2_neg_pixel = true, pv2 = -pv2;
if( diff > difmax )
{
difmax = diff;
imax = i;
jmax = j;
}
norm1 += pv1;
norm2 += pv2;
ndiff += diff;
}
if( im1_neg_pixel )
message("\n*** Warning: a pixel with negative value has been encountered "
"in image 1\n");
if( im2_neg_pixel )
message("\n*** Warning: a pixel with negative value has been encountered "
"in image 2\n");
message("\nMaximal discrepancy \t\t%d",difmax);
message("\n occured at the pixel\t\t(%d,%d)",imax,jmax);
const int pv1 = image1(imax,jmax);
const int pv2 = image2(imax,jmax);
message("\n Image 1 pixel is \t\t%d",pv1);
message("\n Image 2 pixel is \t\t%d",pv2);
message("\n Absolute error v2[i]-v1[i]\t\t%d",pv2-pv1);
message("\n Relative error\t\t\t\t%g\n",
(pv2-pv1)/max((float)abs(pv2+pv1)/2,1e-7) );
message("\nL1 norm of image 1 per pixel \t\t%g",
(double)norm1/image1.npixels);
message("\nL1 norm of image 2 per pixel \t\t%g",
(double)norm2/image2.npixels);
message("\nL1 norm of image1-image2 per pixel\t\t\t%g",
(double)ndiff/image1.npixels);
message("\n||Image1-Image2||/sqrt(||Image1|| ||Image2||)\t%g\n\n",
ndiff/max( sqrt(norm1*norm2), 1e-7 ) );
}
/*
*------------------------------------------------------------------------
* Expansion/Shrinking of the image
*/
// Expand the prototype twice in each
// dimension
// In other words, blow each pixel of the
// prototype to the 2x2 square
void IMAGE::_expand(const IMAGE& prototype)
{
prototype.is_valid();
allocate(2*prototype.nrows,2*prototype.ncols,prototype.bits_per_pixel);
register GRAY * tp = pixels;
register GRAY * pp = prototype.pixels;
register int i,j;
for(i=0; i<prototype.nrows; i++, tp+=ncols)
for(j=0; j<prototype.ncols; j++)
{
register int pixel = *pp++;
*(tp+ncols) = pixel; // Fill out 2x2 square of the
*tp++ = pixel; // blown image with a pixel value
*(tp+ncols) = pixel;
*tp++ = pixel;
}
assert( pp == prototype.pixels + prototype.npixels );
assert( tp == pixels + npixels );
}
// Shrink the prototype twice in each
// dimension. The image row and column
// dimensions are assumed to be even numbers
// Image is shrunk by replacing 2x2 square
// by one pixel with an average intensity
// over the square
void IMAGE::_shrink(const IMAGE& prototype)
{
prototype.is_valid();
if( (prototype.nrows & 1) || (prototype.ncols & 1) )
_error("No of rows and columns in the image to shrink should both be even",
(prototype.info(),0));
allocate(prototype.nrows/2,prototype.ncols/2,prototype.bits_per_pixel);
register GRAY * tp = pixels;
register GRAY * pp = prototype.pixels;
register int i,j;
for(i=0; i<nrows; i++)
{ // Average row-by-row
for(j=0; j<ncols; j++)
*tp = *pp++, *tp++ += *pp++; // Sum of pairs of the prototype pixels
tp -= ncols;
for(j=0; j<ncols; j++, tp++, pp+=2)
{
register int av = *tp + pp[0] + pp[1]; // This is the aver of 4 pixels
*tp = ( av & 2 ? (av >> 2) + 1 : av >> 2 ); // /4 with rounding
}
}
assert( pp == prototype.pixels + prototype.npixels );
assert( tp == pixels + npixels );
}
// Assign another to '*this' resizing
// '*this' as necessary to fit exactly
// Squeezing is done by subsampling (dropping
// some image pixels), while stretching is done
// by repeating pixels/scanlines
// Note, the ratio of sizes of the images
// can be *anything*
IMAGE& IMAGE::coerce(const IMAGE& another)
{
// Make the width even
// width = ( dest_rect.q_width() + 1 ) & (~1);
// height = dest_rect.q_height();
// assert( width > 0 && height > 0 );
// Figure out how much to
// sqeeze/stretch
const float ratio_h = (float)ncols/another.ncols;
const float ratio_v = (float)nrows/another.nrows;
float curr_v = 0, curr_h; // Precise curr. loc. in window coord
card dest_y = 0, dest_x; // approx (rounded-off) coordinates
register GRAY * drp = pixels; // Scanline ptr for fitted image
register const GRAY * spp = another.pixels; // Scanline of 'another' image
// Note, dest_x,dest_y is incremented
// by (inc_v,inc_h), but curr_v is
// incremented by ratio_v, etc.
// Draw horizontally (row-by-row)
while( spp < another.pixels + another.npixels )
{
curr_v += ratio_v;
const GRAY * const spp_end = spp + another.ncols; // end of scanline
int inc_v = (int)curr_v - dest_y;
if( inc_v == 0 ) // inc_v = 0 means this row of
{ // orig_image is to be skipped
spp = spp_end;
continue;
}
dest_y = (int)curr_v;
curr_h = 0;
dest_x = 0;
register GRAY * dpp = drp; // Squeeze/stretch pixels in a scanline
for(; spp < spp_end; spp++)
{
curr_h += ratio_h;
int inc_h = (int)curr_h - dest_x;
dest_x = (int)curr_h;
while( inc_h-- > 0 )
*dpp++ = *spp;
}
if( dpp == drp+ncols-1 ) // Can happen due to roundoff arithm
*dpp++ = *(spp-1);
assert( dpp == drp+ncols );
// Stretching vertically involves
// duplicating scanlines
while( --inc_v > 0 )
memcpy((void *)(drp+ncols),(void *)drp,ncols*sizeof(GRAY)),
drp += ncols;
drp += ncols;
}
if( drp == pixels + npixels-ncols ) // Duplicate the last scanline
memcpy((void *)drp,(void *)(drp-ncols),ncols*sizeof(GRAY)), // in case
drp += ncols; // of roundoff snag
assert( drp == pixels + npixels );
return *this;
}
// Verify the pixels have the value
// that is expected
void verify_pixel_value(const IMAGE& im, const GRAY val)
{
register int i,j;
for(i=0; i<im.q_nrows(); i++)
for(j=0; j<im.q_ncols(); j++)
if( im(i,j) != val )
_error("Pixel [%d,%d] has the value 0x%x different from expected 0x%x",
i,j,im(i,j),val);
}